Async 函数

前言

随着 Node 7 的发布,越来越多的人开始研究据说是异步编程终级解决方案的 async/await,在我们的很多项目中都已经开始使用async函数,下面就来一起理解下Javascript处理异步的async函数。

初识async/await

Async/await 是Javascript编写异步程序的新方法。以往的异步方法无外乎回调函数和Promise。但是Async/await建立于Promise之上,async 函数算是一个语法糖,使异步函数、回调函数在语法上看上去更像同步函数,很多人认为它是异步操作的终极解决方案。

async/await语法

async

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
async function test () {
return 'hello async'
}

const result = test();
console.log(result);

// 可以看到,控制台输出了一个Promise对象
// Promise { 'hello async' }

// 可以then() 链来处理这个 Promise 对象
test().then(v => {
console.log(v); // 输出 hello async
});

在没有 await 的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数一样。所以这个await函数就至关重要了。

await

正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。

1
2
3
4
5
6
async function testAwait() {
return await 123;
}

testAwait().then(v => console.log(v))
// 123

只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行.

1
2
3
4
async function testAwait() {
await Promise.reject('出错了');
await Promise.resolve('hello'); // 不会执行
}

所以当一个 async 函数中有多个 await命令时,如果不想因为一个出错而导致其与的都无法执行,应将await放在try…catch语句中执行

1
2
3
4
5
6
7
8
9
10
11
async function testAwait {
try {
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');
}

testAwait()
.then(v => console.log(v))
// hello world

另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。

1
2
3
4
5
6
7
8
9
10
async function testAwait() {
await Promise.reject('出错了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}

testAwait()
.then(v => console.log(v))
// 出错了
// hello world

并发执行 await 命令

当一个 async 函数中有多个await时,这些 await是继发执行的,只有当前一个await后面的方法执行完毕后,才会执行下一个。
如果我们前后的方法由依赖关系,继发执行是没有问题的,但是如果并没有任何关系的话,这样就会很耗时,所以需要让这些await命令同时执行,也就是并发执行

1
2
3
4
5
6
7
8
// 方法 1 使用Promise.all
let results = await Promise.all([func1(), func2()])

// 方法 2
let func1Promise = func1()
let func2Promise = func2()
let res1 = await func1Promise
let res2 = await func2Promise

错误处理

如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject。
防止出错的方法,也是将其放在try…catch代码块之中。

1
2
3
4
5
6
7
8
9
async function errtest() {
try {
await new Promise(function (resolve, reject) {
throw new Error('出错了');
});
} catch(e) {
}
return await('hello world');
}

如果有多个await命令,可以统一放在try…catch结构中。

1
2
3
4
5
6
7
8
9
10
11
12
async function errtest() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);

console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}

注意点

  1. await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try…catch 代码块中。
  2. 使用async关键字声明异步函数。
  3. await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。
  4. async用来申明里面包裹的内容可以进行同步的方式执行,await则是进行执行顺序控制,每次执行一个await,程序都会暂停等待await返回值,然后再执行之后的await。
  5. 多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

async/await 的优势

then 链

单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。

中间值

一个经常出现的场景是,我们先调起promise1,然后根据返回值,调用promise2,之后再根据这两个Promises得值,调取promise3。
对比一下Promise和async/await的实现方式,就可以发现 async/await代码非常简单,结构清晰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Promise
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return promise2(value1)
.then(value2 => {
// do something
return promise3(value1, value2)
})
})
}

// async/await
const makeRequest = async () => {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}

还有很多的优点,就看大家实际项目中去挖掘了。
thank you

感谢支持,我会不断进步